/* Copyright (C) 2010 Luca Piccinelli
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "generators/cameraInputGenerator.h"
#include "generators/genericGenerator.h"
#include "factory/factories.h"

#include <boost/any.hpp>

#include <sstream>
#include <cassert>
#include <vector>

namespace NAMESPACE{

// ****** CameraInputGenerator implementation **********************************

const CameraInputGenerator::capture_t CameraInputGenerator::DEFAULT_CAPTURE = 0;

//  _____________________________________________________
// |   Standard constructors                             |
// |_____________________________________________________|

CameraInputGenerator::~CameraInputGenerator(){
    if(capture) delete capture;
    free_buffer();
}

CameraInputGenerator::CameraInputGenerator() : AbstractGenerator(),
                                               sz(),
                                               capture(0),
                                               capture_num(DEFAULT_CAPTURE),
                                               is_open(false){
        
    key->setType(TypeParseTraits<cv::Mat*>::name());
}

CameraInputGenerator::CameraInputGenerator(const CameraInputGenerator& cig) : AbstractGenerator(cig),
                                                                              sz(),
                                                                              capture(0),
                                                                              capture_num(cig.capture_num),
                                                                              is_open(false){}

CameraInputGenerator& CameraInputGenerator::operator=(const CameraInputGenerator& cig){
    if(this == &cig) return *this;

    AbstractGenerator::operator =(cig);

    if(is_open) close();
    capture_num = cig.capture_num;

    return *this;
}

// ......................................................


//  _____________________________________________________
// |   Other constructors                                |
// |_____________________________________________________|
CameraInputGenerator::CameraInputGenerator(const IOKey& k,
                                           const capture_t& _capture_num,
                                           const memory_size_t& max_mem) : AbstractGenerator(k, max_mem),
                                                                           sz(),
                                                                           capture(0),
                                                                           capture_num(_capture_num),
                                                                           is_open(false){
    key->setType(TypeParseTraits<cv::Mat*>::name());
}

CameraInputGenerator::CameraInputGenerator(const IOKey& k,
                                           const size_t width,
                                           const size_t height,
                                           const capture_t& _capture_num,
                                           const memory_size_t& max_mem) : AbstractGenerator(k, max_mem),
                                                                           sz(width, height),
                                                                           capture(0),
                                                                           capture_num(_capture_num),
                                                                           is_open(false){
	key->setType(TypeParseTraits<cv::Mat*>::name());
}

// ......................................................

//  _____________________________________________________
// |   Setters                                           |
// |_____________________________________________________|
Generator& CameraInputGenerator::set_key(const std::string& ns, const std::string& key_name){
    AbstractGenerator::set_key(ns, key_name);
    key->setType(TypeParseTraits<cv::Mat*>::name());
    return *this;
}

CameraInputGenerator& CameraInputGenerator::set_capture_num(const capture_t& _capture_num){
    capture_num = _capture_num;
    return *this;
}

// ........................................................

//  _____________________________________________________
// |   Getters                                           |
// |_____________________________________________________|
const CameraInputGenerator::capture_t& CameraInputGenerator::get_capture_num() const{
    return capture_num;
}

bool CameraInputGenerator::is_opened() const{
    return is_open;
}
// .......................................................

//  _____________________________________________________
// |   Private functions                                 |
// |_____________________________________________________|

memory_size_t CameraInputGenerator::measure_memory(){
    if(!generated_buffer->size()) return 0;

    cv::Mat* frame = boost::any_cast<cv::Mat*>((*(generated_buffer->rbegin()))->getElement());
    cv::Size frame_sz = frame->size();

    memory_size_t single_mem_emp = frame_sz.height * frame_sz.width * frame->channels() * sizeof(char);
    return single_mem_emp * generated_buffer->size();
}

bool check_capture_opened(const cv::VideoCapture* capture){
    assert(capture->isOpened());
    if(!capture->isOpened()){
        std::stringstream ss;
        ss << "Can not open the selected camera device: ";
        throw bad_video_capture(ss.str());
    }
    return true;
}

void CameraInputGenerator::free_element(IOElement* el){
    cv::Mat* frame = boost::any_cast<cv::Mat*>( el->getElement() );
    delete frame;
}
// .......................................................

CameraInputGenerator& CameraInputGenerator::open() throw (bad_video_capture){
    if(is_open) return *this;

    try{
        capture = new cv::VideoCapture(capture_num);
    }catch(...){
        std::stringstream ss;
        ss << "The select camera device is busy: ";
        throw bad_video_capture(ss.str());
    }
    is_open = check_capture_opened(capture);

    return *this;
}

CameraInputGenerator& CameraInputGenerator::close(){
    if(!is_open) return *this;

    delete capture;
    capture = 0;
    is_open = false;
    return *this;
}

IOElement* CameraInputGenerator::generate(const IOKey& key){
    if(!is_open) open();

    cv::Mat* frame = new cv::Mat();
    *capture >> *frame;

    if(sz.width && sz.height) {
        cv::Mat* resized = new cv::Mat();
        cv::resize(*frame, *resized, sz);
        std::swap(frame, resized);
        delete resized;
    }

    IOElement* el = new HandledIOElement(frame, key);
    return el;
}

bool CameraInputGenerator::operator==(const Generator& g) const{
    const CameraInputGenerator* cig = dynamic_cast<const CameraInputGenerator*>(&g);
    if(!cig) return false;

    if(AbstractGenerator::operator!=(g)) return false;

    return capture_num == cig->capture_num;
}

bool CameraInputGenerator::operator!=(const Generator& ag) const{
    return !CameraInputGenerator::operator==(ag);
}
// -----------------------------------------------------------------------------

// ****** CameraInputGeneratorFactory implementation ***************************
REGISTER_PARSE_TYPE(CameraInputGenerator);
class CameraInputGeneratorFactory : public ConfigurableFactory<Generator>{
public:
    CameraInputGeneratorFactory() : ConfigurableFactory<Generator>(){
        extern FactoriesPool<Generator>* generator_factories_pool;
        generator_factories_pool->add_factory(TypeParseTraits<CameraInputGenerator>::name(), this);
        IOKey k0, k1, k2, k3;
        k0.set_key(PARAMETER_NS, "iokey");       k0.setType(TypeParseTraits<IOKey>::name());
        k1.set_key(PARAMETER_NS, "size.width");  k1.setType(TypeParseTraits<int>::name());
        k2.set_key(PARAMETER_NS, "size.height"); k2.setType(TypeParseTraits<int>::name());
        k3.set_key(PARAMETER_NS, "device.num");  k3.setType(TypeParseTraits<int>::name());
        cfg_tmplt.add(k0).add(k1).add(k2).add(k3);
    }

    Generator* build() const {
        return new CameraInputGenerator();
    }

    Generator* build(const Configuration& cfg) const throw(bad_parameter){
        IOKey k     = cfg.get_parameter<IOKey>(cfg_tmplt.param_at(0));
        int width   = cfg.get_parameter<int>(cfg_tmplt.param_at(1));
        int height  = cfg.get_parameter<int>(cfg_tmplt.param_at(2));
        int capture = cfg.get_parameter<int>(cfg_tmplt.param_at(3));
        return new CameraInputGenerator(k, width, height, capture);
    }
}instance_of_camera_input_generator_factory;

// -----------------------------------------------------------------------------

REGISTER_PARSE_TYPE(GenericGenerator<cv::Mat>);
GenericGeneratorFactory<cv::Mat, cv::Mat> instance_of_GenericGeneratorFactory;

}
